/*
 * Copyright (c) [2020] Zhang Yansen.All rights reserved.
 *
 * react-tiny-store is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
import React from 'react';
import observe from '@zjxpcyc/observe-ext';

/**
 * 创建一个 store
 * 使用场景：需要将部分动做脱离 react 组件，在外部进行。使用 context 返回值可以任意操作
 * 
 * @param {*|function} initialState - 初始 state 或者 定义 action 的函数
 * @param {function} enhancer - 插件或者用于扩展的函数
 * @returns {Object} 返回 context
 */
export function createStore(initialState, enhancer) {
  const initialEnhancer = initEnhancerCreator(initialState);
  return observe(compose(enhancer, hookEnhancer, initialEnhancer, stateEnhancer));
}

/**
 * 创建一个 store
 * 使用场景：需要在 react 组件内进行各种 动作执行
 * 
 * @param {*|function} initialState - 初始 state 或者 定义 action 的函数
 * @param {function} enhancer - 插件或者用于扩展的函数
 * @returns {hook} 返回 store hook
 */
export function create(initialState, enhancer) {
  return createStore(initialState, enhancer).useStore;
}

/**
 * 初始化函数 Enhancer 的创建者，该 Enhancer 用于初始化状态或提供一个自定义的动作集合。
 * @param {*|Function} initialState - 初始状态对象或一个函数，函数接收context作为参数。
 * @returns {Function} 返回一个 Enhancer 。
 */
function initEnhancerCreator(initialState) {
  return function(context) {
    let actions = context.setState;

    if (typeof initialState == 'function') {
      actions = initialState(context) || context.setState;
    } else {
      context.setState(initialState);
    }

    return {
      ...context,
      actions,
    };
  }
}


/**
 * 创建并管理状态的 Enhancer
 * 
 * @param {Object} context - 上下文对象，至少包含一个publish函数
 * @returns {Object} 返回一个 Enhancer
 */
function stateEnhancer(context) {
  const { publish } = context;

  // 状态
  let state = undefined;

  /**
   * 更新状态函数
   * 
   * @param {*} newState
   */
  const getState = () => state;
  
  /**
   * 更新状态函数
   * 
   * @param {*} newState
   */
  const setState = (newState) => publish(state = typeof newState == 'function' ? newState(state) : newState);

  return {
    ...context,
    getState,
    setState,
  }
}


/**
 * hookEnhancer 用来生成 react hook
 *  
 * @param {Object} context 
 * @returns {Function} 返回 store hook
 */
function hookEnhancer(context) {

  // 小于 React 18 的版本，使用一个简易的 shim
  const useSyncExternalStore = React.useSyncExternalStore || useSyncExternalStoreShim;

  /**
   * 最终返回的 hook
   */
  function useStore(selector, filterActions) {

    // 构造 actions
    const actions = React.useMemo(() => {
      return typeof filterActions == 'function' ? filterActions(context.actions) : context.actions;
    }, []);

    // 构造 getSnapshot 函数
    const getSnapshot = React.useCallback(() => {
      const snapshot = context.getState();
      return typeof selector == 'function' ? selector(snapshot) : snapshot;
    }, []);

    const state = useSyncExternalStore(context.subscribe, getSnapshot);

    const result = React.useMemo(() => ([
      state,
      actions,
      {...context},
    ]), [state]);

    return result;
  }

  return {
    ...context,
    useStore,
  }
}

/**
 * Composes multiple functions from right to left.
 *
 * @param {...Function} funcs - Functions to be composed.
 * @returns {Function} The composed function.
 */
export function compose(...funcs) {
  const fs = funcs.filter(Boolean);

  if (fs.length === 0) {
    return (arg) => arg; // Return identity function if no functions provided
  }

  if (fs.length === 1) {
    return fs[0]; // Return the single function if only one is provided
  }

  return fs.reduce((acc, curr) => {
    return (...args) => acc(curr(...args));
  });
}

/**
 * useSyncExternalStore 的简易实现
 * 
 * @param {Function} subscribe 
 * @param {Function} getSnapshot 
 * @returns useSyncExternalStore
 */
function useSyncExternalStoreShim(subscribe, getSnapshot) {
  const initialState = React.useMemo(getSnapshot, []);
  const [value, setValue] = React.useState(initialState);
  const valueRef = React.useRef();
  valueRef.current = value;
  
  React.useEffect(() => {

    const listener = () => {
      const newVal = getSnapshot();
      if (!Object.is(valueRef.current, newVal)) {
        setValue(newVal);
      }
    }

    // 防止 从初始化 value 到 useEffect 这个时间段内，有 state 更新
    // 所以此处执行一次 listener
    listener();    
    return subscribe(listener);  
  }, []);  
  
  return value;  
}
